/**
 * \file Platform.cc
 * \brief x86 platform expert
 * \author Corey Tabaka
 */

/*
   Copyright 2006 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <platform/Platform.h>
#include <platform/Descriptor.h>
#include <platform/Cpu.h>
#include <platform/Memory.h>
#include <platform/Pic.h>
#include <platform/Pit.h>
#include <platform/Interrupt.h>
#include <platform/Timer.h>
#include <platform/Pci.h>
#include <platform/Acpi.h>
#include <vm/Page.h>
#include <Process.h>
#include <Thread.h>
#include <Namespace.h>
#include <Sync.h>
#include <syscall.h>

#include <stdio.h>

#include <platform/msr.h>
#include <panic.h>
#include <vmware.h>
#include <multiboot.h>

/** Multiboot info structure address */
__data multiboot_info_t *multiBootInfo = 0;

/** Max physical memory address */
uint32 maxPhysicalMemAddr;

/** Flag indicating that the system is hosted in VMware. */
bool inVMware;
Var varVMware("hw.vmware", 0LL, VF_SYSTEM);
Var varVMwareProduct("hw.vmware.product", "", VF_SYSTEM);

/** Kernel build variables. */
Var varBuildDate("kernel.build.date", __DATE__, VF_SYSTEM);
Var varBuildVersion("kernel.build.version", "0.01", VF_SYSTEM);

static void checkVMware(void);
static void checkMBInfo(void);

static struct _init_record {
	const char *name;
	void (*init)(void);
} preinit[] = {
	{ 0, Descriptor::initialize },
	{ 0, checkMBInfo },
	{ 0, checkVMware },
	{ "Processor", Cpu::initialize },
	{ "Interrupt Controller", Pic::initialize },
	{ 0, Pit::initialize },
	{ "System Timer", Timer::initialize },
	{ "Interrupt Manager", Interrupt::initialize },
	{ "System Process", Process::initialize },
	{ "Memory Manager", Memory::initialize },
	{ 0, Thread::initialize },
	{ "PCI Manager", Pci::initialize },
	{ "Namespace Manager", Namespace::initialize },
};

static struct _init_record postinit[] = {
	{ "Switching to mulithreaded mode", Thread::begin },
};

#define PRE_INIT_RECORDS (sizeof(preinit)/sizeof(struct _init_record))
#define POST_INIT_RECORDS (sizeof(postinit)/sizeof(struct _init_record))

// embedded modules
#define DATA_ARRAY_ATTR __attribute__ ((section (".services")))

#if 0
#define THREADTEST_MODULE
#define DATA_ARRAY_NAME threadtest_module
#include "modules/threadtest/module.o.h"
#undef DATA_ARRAY_NAME
#endif

#if 0
#define PCNETPRO_MODULE
#define DATA_ARRAY_NAME pcnetpro_module
#include "modules/pcnetpro/module.o.h"
#undef DATA_ARRAY_NAME
#endif

#if 1
#define SERIAL_MODULE
#define DATA_ARRAY_NAME serial_module
#include "modules/serial/module.o.h"
#undef DATA_ARRAY_NAME
#endif

#if 1
#define DISPLAY_MODULE
#define DATA_ARRAY_NAME display_module
#include "modules/display/module.o.h"
#undef DATA_ARRAY_NAME
#endif

#if 0
#define VM86_MODULE
#define DATA_ARRAY_NAME vm86_module
//#include "modules/vm86test/module.o.h"
#undef DATA_ARRAY_NAME
#endif

#if 1
#define INPUT_MODULE
#define DATA_ARRAY_NAME input_module
#include "modules/i8042/module.o.h"
#undef DATA_ARRAY_NAME
#endif

#if 0
#define IDE_MODULE
#define DATA_ARRAY_NAME ide_module
#include "modules/ide/module.o.h"
#undef DATA_ARRAY_NAME
#endif

#if 1
#define TEST_APP
#define DATA_ARRAY_NAME test_app
#include "services/test/service.lzss.h"
#undef DATA_ARRAY_NAME
#endif

static void init(void *) {
	//printf("Initial namespace\n");
	//Namespace::dumpVars("    ");
	
	//Interrupt::enableLocalInterrupts();
	RefProcess proc = Process::getSystemProcess();
	
	printf("    Loading modules\n");
	// load modules
	proc->loadPeModule(serial_module, sizeof(serial_module));
	
	Page::dump();
		
	// ACPI needs to be initialized after timers are running
	Acpi::initialize();
	
	//proc->loadPeModule(ide_module, sizeof(ide_module));
	proc->loadPeModule(input_module, sizeof(input_module));
	proc->loadPeModule(display_module, sizeof(display_module));
	//proc->loadPeModule(threadtest_module, sizeof(threadtest_module));
	//proc->loadPeModule(pcnetpro_module, sizeof(pcnetpro_module));
	//proc->loadPeModule(vm86_module, sizeof(vm86_module));
	printf("    Done loading modules\n");
	
	printf("    Loading services\n");
	
	RefProcess test = Process::createProcess("Test App");
	test->getPageMap().initialize(PROCESS_USER_SPACE, 0xffffffff);
	test->loadPeExe(test_app, sizeof(test_app), true);
}

void Platform::initialize(void) {
	printf("Initializing platform\n");
	
	Interrupt::disableLocalInterrupts();
	
	// register variables
	Namespace::registerVar(varBuildDate);
	Namespace::registerVar(varBuildVersion);
	Namespace::registerVar(varVMware);
	Namespace::registerVar(varVMwareProduct);
	
	for (uint i=0; i < PRE_INIT_RECORDS; i++) {
		if (preinit[i].name) {
			printf("    %s\n", preinit[i].name);
		}
		
		preinit[i].init();
	}
	
	RefThread thread = Thread::createThread(init, 0, "Init",
		Process::getSystemProcess());
	thread->run();
	thread = nullThread; // allow the thread to GC
	
	for (uint i=0; i < POST_INIT_RECORDS; i++) {
		if (postinit[i].name) {
			printf("    %s\n", postinit[i].name);
		}
		
		postinit[i].init();
	}
}

static const char *vmWareProducts[] = {
	"(Unrecognized Product Line)",
	"Express",
	"ESX Server",
	"GSX Server",
	"Workstation/Player",
};

static void checkVMware(void) {
	uint32 regs[4];
	regs[0] = VMWARE_MAGIC;
	regs[1] = 0xc0de;	/* don't care */
	regs[2] = VMCMD_GET_VERSION;
	regs[3] = VMWARE_PORT;
	vm_cmd(regs);
	
	if (regs[1] == VMWARE_MAGIC) {
		inVMware = true;
		varVMware = true;
		
		varVMwareProduct = vmWareProducts[regs[2]];
		
		printf("    Detected VMware %s\n", varVMwareProduct.svalue);
		
		// host screen res
		regs[0] = VMWARE_MAGIC;
		regs[1] = 0xc0de;	/* don't care */
		regs[2] = VMCMD_GET_HOSTRES;
		regs[3] = VMWARE_PORT;
		vm_cmd(regs);
		
		printf("        Host resolution %ux%u\n", regs[0] >> 16,
			regs[0] & 0xffff);
	}
}

static void checkMBInfo(void) {
	if (multiBootInfo) {
		maxPhysicalMemAddr = 0;
		
		//printf("Multiboot Flags: %08x\n", multiBootInfo->flags);
		
		if (multiBootInfo->flags & MB_INFO_MEM_SIZE) {
			maxPhysicalMemAddr = multiBootInfo->mem_upper * 1024;
		}
		
		if (multiBootInfo->flags & MB_INFO_MMAP) {
			memory_map_t *mmap =
				(memory_map_t *) (multiBootInfo->mmap_addr - 4);
			
			//printf("mmap length: %u\n", multiBootInfo->mmap_length);
			
			for (uint32 i=0;
				i < multiBootInfo->mmap_length/sizeof(memory_map_t); i++) {
				/*printf("base=%08x, length=%08x, type=%02x\n",
					mmap[i].base_addr_low, mmap[i].length_low, mmap[i].type);
				*/
				
				if (mmap[i].type == 1
					&& mmap[i].base_addr_low >= maxPhysicalMemAddr) {
					maxPhysicalMemAddr = mmap[i].base_addr_low
						+ mmap[i].length_low;
				} else {
					// reserve
				}
			}
		}
	}
}

// TODO: temporary errno to keep math lib functions happy

int errno;

extern "C"
int *__geterrno(void) {
	return &errno;
}
